// 文件路径: dbms/service/UdfCompilerService.java
package com.dbms.service;

import javax.tools.JavaCompiler;
import javax.tools.ToolProvider;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.UUID;

/**
 * 【已修正】
 * 负责动态编译用户提供的 Java 源代码字符串。
 * <p>
 * 这个服务类封装了使用 Java Compiler API (JSR 199) 的复杂性，
 * 允许在运行时将一个字符串形式的Java源代码编译成字节码（.class文件）。
 * 这对于实现用户自定义函数（UDF）等需要动态加载代码的功能至关重要。
 * <p>
 * 修复了 compiler.run() 方法的参数类型不匹配问题。
 */
public class UdfCompilerService {

    /**
     * 编译用户提供的UDF源代码。
     * <p>
     * 该方法执行以下步骤：
     * 1. 获取系统Java编译器。
     * 2. 创建一个唯一的临时目录来存放源文件和编译后的class文件。
     * 3. 将源代码字符串写入一个临时的 .java 文件中。
     * 4. 调用编译器来编译这个 .java 文件。
     * 5. 捕获并检查编译结果，如果失败则抛出包含错误信息的异常。
     * 6. 如果编译成功，返回临时目录的路径，调用者后续可以使用这个路径来加载编译好的类。
     *
     * @param sourceCode UDF的完整Java源代码字符串。
     * @return 编译成功后，返回包含编译后 .class 文件的临时目录的 {@link Path} 对象。
     * @throws Exception 如果找不到编译器、文件操作失败或编译失败。
     */
    public static Path compile(String sourceCode) throws Exception {
        // 获取系统Java编译器实例
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        if (compiler == null) {
            // 如果返回null，通常意味着当前环境是JRE而不是JDK
            throw new IllegalStateException(
                    "无法找到系统Java编译器。请确保您运行的是JDK而不是JRE。\n" +
                            "检查您的项目配置，确保它使用的是JDK的java.exe。"
            );
        }

        // 1. 创建一个唯一的临时目录，以避免文件名冲突
        Path tempDir = Files.createTempDirectory("udf_compiler_" + UUID.randomUUID());
        // 在临时目录中定义源文件的路径，文件名固定为 CustomUdf.java
        Path sourceFile = tempDir.resolve("CustomUdf.java");

        // 2. 将源代码字符串写入到临时的 .java 文件中
        Files.writeString(sourceFile, sourceCode);

        // 3. 准备一个 ByteArrayOutputStream 来捕获编译器的标准错误输出流
        ByteArrayOutputStream errStream = new ByteArrayOutputStream();

        // 4. 【核心修正】调用编译器执行编译任务
        //    compiler.run() 方法接受多个参数来配置编译过程
        int compilationResult = compiler.run(
                null,       // stdin:  标准输入流，这里不需要，传入null
                null,       // stdout: 标准输出流，这里不需要，传入null
                errStream,  // stderr: 标准错误流，传入我们准备的流来捕获错误信息
                "-d", tempDir.toString(), // 编译选项: -d 指定class文件的输出目录
                sourceFile.toString()     // 要编译的源文件路径
        );

        // 5. 检查编译结果。返回0表示成功。
        if (compilationResult != 0) {
            // 如果编译失败，从错误流中获取详细的编译错误信息
            String compileError = errStream.toString();
            // 编译失败后，清理创建的临时目录和文件
            deleteDirectory(tempDir.toFile());
            // 抛出异常，将编译错误信息传递给调用者
            throw new Exception("UDF 编译失败:\n" + compileError);
        }

        // 6. 编译成功，返回存放 .class 文件的临时目录的路径
        return tempDir;
    }

    /**
     * 辅助方法：递归删除一个目录及其所有内容。
     *
     * @param directory 要删除的目录的 {@link File} 对象。
     */
    public static void deleteDirectory(File directory) {
        // 如果目录不存在，则直接返回
        if (!directory.exists()) {
            return;
        }
        // 获取目录下的所有文件和子目录
        File[] allContents = directory.listFiles();
        if (allContents != null) {
            for (File file : allContents) {
                // 递归删除子文件或子目录
                deleteDirectory(file);
            }
        }
        // 删除空目录本身
        directory.delete();
    }
}